/*-------------------------------------------------------------------------
 *
 * execTuples.c
 *	  处理TupleTableSlots的例程。这些用于与元组相关的资源
 *	  管理（例如，释放磁盘缓冲区中元组的缓冲区引脚，或释放占用暂态
 *	  元组的内存）。Slots还提供访问抽象，允许我们实现
 *	  “虚拟”元组以减少数据复制开销。
 *
 *	  处理元组类型信息的例程。目前，
 *	  元组的类型信息是FormData_pg_attribute的数组。
 *	  例程在操作元组时需要此信息
 *	  （getattribute、formtuple等）。
 *
 *
 *	 如何表例程工作的示例
 *		假设我们有一个查询，例如SELECT emp.name FROM emp，并且我们有
 *		一个查询计划中的单个SeqScan节点。
 *
 *		在ExecutorStart()
 *		----------------
 *
 *		- ExecInitSeqScan()调用ExecInitScanTupleSlot()来构造
 *		  由访问方法返回的元组的TupleTableSlots，并且
 *		  ExecInitResultTypeTL()来定义节点的返回
 *		  类型。ExecAssignScanProjectionInfo()将如有必要，
 *		  为执行目标列表投影而生成另一个TupleTableSlot。
 *
 *		在ExecutorRun()期间
 *		----------------
 *		- SeqNext()调用ExecStoreBufferHeapTuple()将访问方法
 *		  返回的元组放入扫描元组槽中。
 *
 *		- ExecSeqScan()（通过ExecScan），如有必要，调用ExecProject(),
 *		  将投影的结果放入结果元组槽中。如果不必要，它直接返回
 *		  SeqNext()返回的槽。
 *
 *		- ExecutePlan()调用输出函数。
 *
 *		在执行器代码中需要注意的重要事项是，指向包含元组的槽的指针
 *		是如何传递的，而不是元组本身。这便于相关信息的传递
 *		（例如，元组是否应该被pfreed，哪个缓冲区包含
 *		该元组，元组的元组描述符等）。这还使我们能够
 *		在许多情况下避免物理构建投影元组。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/execTuples.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heaptoast.h"
#include "access/htup_details.h"
#include "access/tupdesc_details.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
#include "utils/builtins.h"
#include "utils/expandeddatum.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"

static TupleDesc fc_ExecTypeFromTLInternal(List *fc_targetList,
										bool fc_skipjunk);
static pg_attribute_always_inline void fc_slot_deform_heap_tuple(TupleTableSlot *fc_slot, HeapTuple fc_tuple, uint32 *fc_offp,
															  int fc_natts);
static inline void fc_tts_buffer_heap_store_tuple(TupleTableSlot *fc_slot,
											   HeapTuple fc_tuple,
											   Buffer fc_buffer,
											   bool fc_transfer_pin);
static void fc_tts_heap_store_tuple(TupleTableSlot *fc_slot, HeapTuple fc_tuple, bool fc_shouldFree);


const TupleTableSlotOps TTSOpsVirtual;
const TupleTableSlotOps TTSOpsHeapTuple;
const TupleTableSlotOps TTSOpsMinimalTuple;
const TupleTableSlotOps TTSOpsBufferHeapTuple;


/*
 * TupleTableSlotOps 实现。
 */

/*
 * VirtualTupleTableSlot 的 TupleTableSlotOps 实现。
 */
static void fc_tts_virtual_init(TupleTableSlot *fc_slot)
{
}

static void fc_tts_virtual_release(TupleTableSlot *fc_slot)
{
}

static void fc_tts_virtual_clear(TupleTableSlot *fc_slot)
{
	if (unlikely(TTS_SHOULDFREE(fc_slot)))
	{
		VirtualTupleTableSlot *fc_vslot = (VirtualTupleTableSlot *) fc_slot;

		pfree(fc_vslot->data);
		fc_vslot->data = NULL;

		fc_slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
	}

	fc_slot->tts_nvalid = 0;
	fc_slot->tts_flags |= TTS_FLAG_EMPTY;
	ItemPointerSetInvalid(&fc_slot->tts_tid);
}

/*
 * VirtualTupleTableSlots 总是具有完全填充的 tts_values 和
 * tts_isnull 数组。因此这个函数永远不应该被调用。
 */
static void fc_tts_virtual_getsomeattrs(TupleTableSlot *fc_slot, int fc_natts)
{
	elog(ERROR, "getsomeattrs is not required to be called on a virtual tuple table slot");
}

/*
 * VirtualTupleTableSlots 从不提供系统属性（除非是按通用方式处理的，例如 tableoid）。
 * 我们通常不应该到达这里，但如果到了，请提供一个用户友好的消息。
 */
static Datum fc_tts_virtual_getsysattr(TupleTableSlot *fc_slot, int fc_attnum, bool *fc_isnull)
{
	Assert(!TTS_EMPTY(fc_slot));

	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("cannot retrieve a system column in this context")));

	return 0;					/* 静默编译器警告 */
}

/*
 * 要使一个虚拟槽物化，所有未按值传递的 datums 必须复制到槽的内存上下文中。
 * 为此，计算所需的大小，并分配足够的内存以存储所有属性。
 * 这对缓存命中率有好处，但更重要的是只需要内存分配/释放。
 */
static void fc_tts_virtual_materialize(TupleTableSlot *fc_slot)
{
	VirtualTupleTableSlot *fc_vslot = (VirtualTupleTableSlot *) fc_slot;
	TupleDesc	fc_desc = fc_slot->tts_tupleDescriptor;
	Size		fc_sz = 0;
	char	   *fc_data;

	/* 已经物化 */
	if (TTS_SHOULDFREE(fc_slot))
		return;

	/* 计算所需的内存大小 */
	for (int fc_natt = 0; fc_natt < fc_desc->natts; fc_natt++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_desc, fc_natt);
		Datum		fc_val;

		if (fc_att->attbyval || fc_slot->tts_isnull[fc_natt])
			continue;

		fc_val = fc_slot->tts_values[fc_natt];

		if (fc_att->attlen == -1 &&
			VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_val)))
		{
			/*
			 * 我们希望展平扩展值，以便物化的
			 * 槽不依赖于它。
			 */
			fc_sz = att_align_nominal(fc_sz, fc_att->attalign);
			fc_sz += EOH_get_flat_size(DatumGetEOHP(fc_val));
		}
		else
		{
			fc_sz = att_align_nominal(fc_sz, fc_att->attalign);
			fc_sz = att_addlength_datum(fc_sz, fc_att->attlen, fc_val);
		}
	}

	/* 所有数据都是按值 */
	if (fc_sz == 0)
		return;

	/* 分配内存 */
	fc_vslot->data = fc_data = MemoryContextAlloc(fc_slot->tts_mcxt, fc_sz);
	fc_slot->tts_flags |= TTS_FLAG_SHOULDFREE;

	/* 并将所有属性复制到预分配的空间中 */
	for (int fc_natt = 0; fc_natt < fc_desc->natts; fc_natt++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_desc, fc_natt);
		Datum		fc_val;

		if (fc_att->attbyval || fc_slot->tts_isnull[fc_natt])
			continue;

		fc_val = fc_slot->tts_values[fc_natt];

		if (fc_att->attlen == -1 &&
			VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_val)))
		{
			Size		fc_data_length;

			/*
			 * 我们希望展平扩展值，以便物化的
			 * 槽不依赖于它。
			 */
			ExpandedObjectHeader *fc_eoh = DatumGetEOHP(fc_val);

			fc_data = (char *) att_align_nominal(fc_data,
											  fc_att->attalign);
			fc_data_length = EOH_get_flat_size(fc_eoh);
			EOH_flatten_into(fc_eoh, fc_data, fc_data_length);

			fc_slot->tts_values[fc_natt] = PointerGetDatum(fc_data);
			fc_data += fc_data_length;
		}
		else
		{
			Size		fc_data_length = 0;

			fc_data = (char *) att_align_nominal(fc_data, fc_att->attalign);
			fc_data_length = att_addlength_datum(fc_data_length, fc_att->attlen, fc_val);

			memcpy(fc_data, DatumGetPointer(fc_val), fc_data_length);

			fc_slot->tts_values[fc_natt] = PointerGetDatum(fc_data);
			fc_data += fc_data_length;
		}
	}
}

static void fc_tts_virtual_copyslot(TupleTableSlot *fc_dstslot, TupleTableSlot *fc_srcslot)
{
	TupleDesc	fc_srcdesc = fc_srcslot->tts_tupleDescriptor;

	Assert(fc_srcdesc->natts <= fc_dstslot->tts_tupleDescriptor->natts);

	fc_tts_virtual_clear(fc_dstslot);

	slot_getallattrs(fc_srcslot);

	for (int fc_natt = 0; fc_natt < fc_srcdesc->natts; fc_natt++)
	{
		fc_dstslot->tts_values[fc_natt] = fc_srcslot->tts_values[fc_natt];
		fc_dstslot->tts_isnull[fc_natt] = fc_srcslot->tts_isnull[fc_natt];
	}

	fc_dstslot->tts_nvalid = fc_srcdesc->natts;
	fc_dstslot->tts_flags &= ~TTS_FLAG_EMPTY;

	/* 确保存储不依赖于外部内存 */
	fc_tts_virtual_materialize(fc_dstslot);
}

static HeapTuple fc_tts_virtual_copy_heap_tuple(TupleTableSlot *fc_slot)
{
	Assert(!TTS_EMPTY(fc_slot));

	return heap_form_tuple(fc_slot->tts_tupleDescriptor,
						   fc_slot->tts_values,
						   fc_slot->tts_isnull);
}

static MinimalTuple fc_tts_virtual_copy_minimal_tuple(TupleTableSlot *fc_slot)
{
	Assert(!TTS_EMPTY(fc_slot));

	return heap_form_minimal_tuple(fc_slot->tts_tupleDescriptor,
								   fc_slot->tts_values,
								   fc_slot->tts_isnull);
}


/*
 * TupleTableSlotOps 实现用于 HeapTupleTableSlot。
 */

static void fc_tts_heap_init(TupleTableSlot *fc_slot)
{
}

static void fc_tts_heap_release(TupleTableSlot *fc_slot)
{
}

static void fc_tts_heap_clear(TupleTableSlot *fc_slot)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;

	/* 如果允许，释放堆元组的内存。 */
	if (TTS_SHOULDFREE(fc_slot))
	{
		heap_freetuple(fc_hslot->tuple);
		fc_slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
	}

	fc_slot->tts_nvalid = 0;
	fc_slot->tts_flags |= TTS_FLAG_EMPTY;
	ItemPointerSetInvalid(&fc_slot->tts_tid);
	fc_hslot->off = 0;
	fc_hslot->tuple = NULL;
}

static void fc_tts_heap_getsomeattrs(TupleTableSlot *fc_slot, int fc_natts)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	fc_slot_deform_heap_tuple(fc_slot, fc_hslot->tuple, &fc_hslot->off, fc_natts);
}

static Datum fc_tts_heap_getsysattr(TupleTableSlot *fc_slot, int fc_attnum, bool *fc_isnull)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	/*
	 * 在某些代码路径中，有可能遇到未物化的
	 * 槽，因此我们无法检索系统列。
	 */
	if (!fc_hslot->tuple)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot retrieve a system column in this context")));

	return heap_getsysattr(fc_hslot->tuple, fc_attnum,
						   fc_slot->tts_tupleDescriptor, fc_isnull);
}

static void fc_tts_heap_materialize(TupleTableSlot *fc_slot)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;
	MemoryContext fc_oldContext;

	Assert(!TTS_EMPTY(fc_slot));

	/* 如果槽的元组已经物化，无需做任何事情。 */
	if (TTS_SHOULDFREE(fc_slot))
		return;

	fc_oldContext = MemoryContextSwitchTo(fc_slot->tts_mcxt);

	/*
	 * 必须从头变形，否则 tts_values[] 条目可能指向
	 * 未物化的元组（当访问时可能消失）。
	 */
	fc_slot->tts_nvalid = 0;
	fc_hslot->off = 0;

	if (!fc_hslot->tuple)
		fc_hslot->tuple = heap_form_tuple(fc_slot->tts_tupleDescriptor,
									   fc_slot->tts_values,
									   fc_slot->tts_isnull);
	else
	{
		/*
		 * 此槽中包含的元组并未在给定槽的内存
		 * 上下文中分配（否则它会设置 TTS_SHOULDFREE）。
		 * 将元组复制到给定槽的内存上下文中。
		 */
		fc_hslot->tuple = heap_copytuple(fc_hslot->tuple);
	}

	fc_slot->tts_flags |= TTS_FLAG_SHOULDFREE;

	MemoryContextSwitchTo(fc_oldContext);
}

static void fc_tts_heap_copyslot(TupleTableSlot *fc_dstslot, TupleTableSlot *fc_srcslot)
{
	HeapTuple	fc_tuple;
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_dstslot->tts_mcxt);
	fc_tuple = ExecCopySlotHeapTuple(fc_srcslot);
	MemoryContextSwitchTo(fc_oldcontext);

	ExecStoreHeapTuple(fc_tuple, fc_dstslot, true);
}

static HeapTuple fc_tts_heap_get_heap_tuple(TupleTableSlot *fc_slot)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));
	if (!fc_hslot->tuple)
		fc_tts_heap_materialize(fc_slot);

	return fc_hslot->tuple;
}

static HeapTuple fc_tts_heap_copy_heap_tuple(TupleTableSlot *fc_slot)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));
	if (!fc_hslot->tuple)
		fc_tts_heap_materialize(fc_slot);

	return heap_copytuple(fc_hslot->tuple);
}

static MinimalTuple fc_tts_heap_copy_minimal_tuple(TupleTableSlot *fc_slot)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;

	if (!fc_hslot->tuple)
		fc_tts_heap_materialize(fc_slot);

	return minimal_tuple_from_heap_tuple(fc_hslot->tuple);
}

static void fc_tts_heap_store_tuple(TupleTableSlot *fc_slot, HeapTuple fc_tuple, bool fc_shouldFree)
{
	HeapTupleTableSlot *fc_hslot = (HeapTupleTableSlot *) fc_slot;

	fc_tts_heap_clear(fc_slot);

	fc_slot->tts_nvalid = 0;
	fc_hslot->tuple = fc_tuple;
	fc_hslot->off = 0;
	fc_slot->tts_flags &= ~(TTS_FLAG_EMPTY | TTS_FLAG_SHOULDFREE);
	fc_slot->tts_tid = fc_tuple->t_self;

	if (fc_shouldFree)
		fc_slot->tts_flags |= TTS_FLAG_SHOULDFREE;
}


/*
 * TupleTableSlotOps 实现用于 MinimalTupleTableSlot。
 */

static void fc_tts_minimal_init(TupleTableSlot *fc_slot)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;

	/*
	 * 初始化堆元组指针以访问槽中包含的最小
	 * 元组的属性，就像它是一个堆元组一样。
	 */
	fc_mslot->tuple = &fc_mslot->minhdr;
}

static void fc_tts_minimal_release(TupleTableSlot *fc_slot)
{
}

static void fc_tts_minimal_clear(TupleTableSlot *fc_slot)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;

	if (TTS_SHOULDFREE(fc_slot))
	{
		heap_free_minimal_tuple(fc_mslot->mintuple);
		fc_slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
	}

	fc_slot->tts_nvalid = 0;
	fc_slot->tts_flags |= TTS_FLAG_EMPTY;
	ItemPointerSetInvalid(&fc_slot->tts_tid);
	fc_mslot->off = 0;
	fc_mslot->mintuple = NULL;
}

static void fc_tts_minimal_getsomeattrs(TupleTableSlot *fc_slot, int fc_natts)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	fc_slot_deform_heap_tuple(fc_slot, fc_mslot->tuple, &fc_mslot->off, fc_natts);
}

static Datum fc_tts_minimal_getsysattr(TupleTableSlot *fc_slot, int fc_attnum, bool *fc_isnull)
{
	Assert(!TTS_EMPTY(fc_slot));

	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("cannot retrieve a system column in this context")));

	return 0;					/* 静默编译器警告 */
}

static void fc_tts_minimal_materialize(TupleTableSlot *fc_slot)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;
	MemoryContext fc_oldContext;

	Assert(!TTS_EMPTY(fc_slot));

	/* 如果槽的元组已经物化，无需做任何事情。 */
	if (TTS_SHOULDFREE(fc_slot))
		return;

	fc_oldContext = MemoryContextSwitchTo(fc_slot->tts_mcxt);

	/*
	 * 必须从头变形，否则 tts_values[] 条目可能指向
	 * 未物化的元组（当访问时可能消失）。
	 */
	fc_slot->tts_nvalid = 0;
	fc_mslot->off = 0;

	if (!fc_mslot->mintuple)
	{
		fc_mslot->mintuple = heap_form_minimal_tuple(fc_slot->tts_tupleDescriptor,
												  fc_slot->tts_values,
												  fc_slot->tts_isnull);
	}
	else
	{
		/*
		 * 此槽中包含的最小元组并未在给定槽的
		 * 内存上下文中分配（否则它会设置 TTS_SHOULDFREE）。
		 * 将最小元组复制到给定槽的内存上下文中。
		 */
		fc_mslot->mintuple = heap_copy_minimal_tuple(fc_mslot->mintuple);
	}

	fc_slot->tts_flags |= TTS_FLAG_SHOULDFREE;

	Assert(fc_mslot->tuple == &fc_mslot->minhdr);

	fc_mslot->minhdr.t_len = fc_mslot->mintuple->t_len + MINIMAL_TUPLE_OFFSET;
	fc_mslot->minhdr.t_data = (HeapTupleHeader) ((char *) fc_mslot->mintuple - MINIMAL_TUPLE_OFFSET);

	MemoryContextSwitchTo(fc_oldContext);
}

static void fc_tts_minimal_copyslot(TupleTableSlot *fc_dstslot, TupleTableSlot *fc_srcslot)
{
	MemoryContext fc_oldcontext;
	MinimalTuple fc_mintuple;

	fc_oldcontext = MemoryContextSwitchTo(fc_dstslot->tts_mcxt);
	fc_mintuple = ExecCopySlotMinimalTuple(fc_srcslot);
	MemoryContextSwitchTo(fc_oldcontext);

	ExecStoreMinimalTuple(fc_mintuple, fc_dstslot, true);
}

static MinimalTuple fc_tts_minimal_get_minimal_tuple(TupleTableSlot *fc_slot)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;

	if (!fc_mslot->mintuple)
		fc_tts_minimal_materialize(fc_slot);

	return fc_mslot->mintuple;
}

static HeapTuple fc_tts_minimal_copy_heap_tuple(TupleTableSlot *fc_slot)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;

	if (!fc_mslot->mintuple)
		fc_tts_minimal_materialize(fc_slot);

	return heap_tuple_from_minimal_tuple(fc_mslot->mintuple);
}

static MinimalTuple fc_tts_minimal_copy_minimal_tuple(TupleTableSlot *fc_slot)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;

	if (!fc_mslot->mintuple)
		fc_tts_minimal_materialize(fc_slot);

	return heap_copy_minimal_tuple(fc_mslot->mintuple);
}

static void fc_tts_minimal_store_tuple(TupleTableSlot *fc_slot, MinimalTuple fc_mtup, bool fc_shouldFree)
{
	MinimalTupleTableSlot *fc_mslot = (MinimalTupleTableSlot *) fc_slot;

	fc_tts_minimal_clear(fc_slot);

	Assert(!TTS_SHOULDFREE(fc_slot));
	Assert(TTS_EMPTY(fc_slot));

	fc_slot->tts_flags &= ~TTS_FLAG_EMPTY;
	fc_slot->tts_nvalid = 0;
	fc_mslot->off = 0;

	fc_mslot->mintuple = fc_mtup;
	Assert(fc_mslot->tuple == &fc_mslot->minhdr);
	fc_mslot->minhdr.t_len = fc_mtup->t_len + MINIMAL_TUPLE_OFFSET;
	fc_mslot->minhdr.t_data = (HeapTupleHeader) ((char *) fc_mtup - MINIMAL_TUPLE_OFFSET);
	/* 不需要设置 t_self 或 t_tableOid，因为我们不允许访问 */

	if (fc_shouldFree)
		fc_slot->tts_flags |= TTS_FLAG_SHOULDFREE;
}


/*
 * TupleTableSlotOps 实现用于 BufferHeapTupleTableSlot。
 */

static void fc_tts_buffer_heap_init(TupleTableSlot *fc_slot)
{
}

static void fc_tts_buffer_heap_release(TupleTableSlot *fc_slot)
{
}

static void fc_tts_buffer_heap_clear(TupleTableSlot *fc_slot)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

	/*
	 * 如果允许，释放堆元组的内存。来自缓冲区的元组
	 * 永远不能被释放。但我们可能从缓冲区物化了一个元组。
	 * 这样的元组可以被释放。
	 */
	if (TTS_SHOULDFREE(fc_slot))
	{
		/* 在物化元组时，我们应该已经解锁缓冲区。 */
		Assert(!BufferIsValid(fc_bslot->buffer));

		heap_freetuple(fc_bslot->base.tuple);
		fc_slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
	}

	if (BufferIsValid(fc_bslot->buffer))
		ReleaseBuffer(fc_bslot->buffer);

	fc_slot->tts_nvalid = 0;
	fc_slot->tts_flags |= TTS_FLAG_EMPTY;
	ItemPointerSetInvalid(&fc_slot->tts_tid);
	fc_bslot->base.tuple = NULL;
	fc_bslot->base.off = 0;
	fc_bslot->buffer = InvalidBuffer;
}

static void fc_tts_buffer_heap_getsomeattrs(TupleTableSlot *fc_slot, int fc_natts)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	fc_slot_deform_heap_tuple(fc_slot, fc_bslot->base.tuple, &fc_bslot->base.off, fc_natts);
}

static Datum fc_tts_buffer_heap_getsysattr(TupleTableSlot *fc_slot, int fc_attnum, bool *fc_isnull)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	/*
	 * 在某些代码路径中，有可能遇到未物化的
	 * 槽，因此我们无法检索系统列。
	 */
	if (!fc_bslot->base.tuple)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot retrieve a system column in this context")));

	return heap_getsysattr(fc_bslot->base.tuple, fc_attnum,
						   fc_slot->tts_tupleDescriptor, fc_isnull);
}

static void fc_tts_buffer_heap_materialize(TupleTableSlot *fc_slot)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;
	MemoryContext fc_oldContext;

	Assert(!TTS_EMPTY(fc_slot));

	/* 如果槽的元组已经物化，无需做任何事情。 */
	if (TTS_SHOULDFREE(fc_slot))
		return;

	fc_oldContext = MemoryContextSwitchTo(fc_slot->tts_mcxt);

	/*
	 * 必须从头变形，否则 tts_values[] 条目可能指向
	 * 未物化的元组（当访问时可能消失）。
	 */
	fc_bslot->base.off = 0;
	fc_slot->tts_nvalid = 0;

	if (!fc_bslot->base.tuple)
	{
		/*
		 * 通常 BufferHeapTupleTableSlot 应该有一个元组 + 缓冲区
		 * 与之相关联，除非它被物化（这将在上面返回）。
		 * 但当允许在缓冲槽中存储虚拟元组时，这也是有用的，
		 * 而且它也需要是可物化的。
		 */
		fc_bslot->base.tuple = heap_form_tuple(fc_slot->tts_tupleDescriptor,
											fc_slot->tts_values,
											fc_slot->tts_isnull);
	}
	else
	{
		fc_bslot->base.tuple = heap_copytuple(fc_bslot->base.tuple);

		/*
		 * 存储在 BufferHeapTupleTableSlot 中的堆元组应该有一个
		 * 与之相关联的缓冲区，除非它被物化或虚拟。
		 */
		if (likely(BufferIsValid(fc_bslot->buffer)))
			ReleaseBuffer(fc_bslot->buffer);
		fc_bslot->buffer = InvalidBuffer;
	}

	/*
	 * 我们不会设置 TTS_FLAG_SHOULDFREE，直到释放缓冲区结束，
	 * 如果有的话。这避免了将出现的瞬态状态违反我们的
	 * 断言，即带有 TTS_FLAG_SHOULDFREE 的槽不拥有缓冲区。
	 * 在不太可能的情况下，上面的 ReleaseBuffer() 出现错误，
	 * 我们实际上会泄漏复制的元组，但这似乎相当无害。
	 */
	fc_slot->tts_flags |= TTS_FLAG_SHOULDFREE;

	MemoryContextSwitchTo(fc_oldContext);
}

static void fc_tts_buffer_heap_copyslot(TupleTableSlot *fc_dstslot, TupleTableSlot *fc_srcslot)
{
	BufferHeapTupleTableSlot *fc_bsrcslot = (BufferHeapTupleTableSlot *) fc_srcslot;
	BufferHeapTupleTableSlot *fc_bdstslot = (BufferHeapTupleTableSlot *) fc_dstslot;

	/*
	 * 如果源槽是不同类型，或者是已经物化/虚拟的缓冲槽，
	 * 则需重新复制元组。否则，引用缓冲中的元组。
	 */
	if (fc_dstslot->tts_ops != fc_srcslot->tts_ops ||
		TTS_SHOULDFREE(fc_srcslot) ||
		!fc_bsrcslot->base.tuple)
	{
		MemoryContext fc_oldContext;

		ExecClearTuple(fc_dstslot);
		fc_dstslot->tts_flags &= ~TTS_FLAG_EMPTY;
		fc_oldContext = MemoryContextSwitchTo(fc_dstslot->tts_mcxt);
		fc_bdstslot->base.tuple = ExecCopySlotHeapTuple(fc_srcslot);
		fc_dstslot->tts_flags |= TTS_FLAG_SHOULDFREE;
		MemoryContextSwitchTo(fc_oldContext);
	}
	else
	{
		Assert(BufferIsValid(fc_bsrcslot->buffer));

		fc_tts_buffer_heap_store_tuple(fc_dstslot, fc_bsrcslot->base.tuple,
									fc_bsrcslot->buffer, false);

		/*
		 * 源元组的 HeapTupleData 部分的生命周期可能比目标槽要短。
		 * 因此，将 HeapTuple 复制到我们槽的 tupdata 中，
		 * 该数据保证能存活足够长的时间（但仍将指向缓冲区）。
		 */
		memcpy(&fc_bdstslot->base.tupdata, fc_bdstslot->base.tuple, sizeof(HeapTupleData));
		fc_bdstslot->base.tuple = &fc_bdstslot->base.tupdata;
	}
}

static HeapTuple fc_tts_buffer_heap_get_heap_tuple(TupleTableSlot *fc_slot)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	if (!fc_bslot->base.tuple)
		fc_tts_buffer_heap_materialize(fc_slot);

	return fc_bslot->base.tuple;
}

static HeapTuple fc_tts_buffer_heap_copy_heap_tuple(TupleTableSlot *fc_slot)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	if (!fc_bslot->base.tuple)
		fc_tts_buffer_heap_materialize(fc_slot);

	return heap_copytuple(fc_bslot->base.tuple);
}

static MinimalTuple fc_tts_buffer_heap_copy_minimal_tuple(TupleTableSlot *fc_slot)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

	Assert(!TTS_EMPTY(fc_slot));

	if (!fc_bslot->base.tuple)
		fc_tts_buffer_heap_materialize(fc_slot);

	return minimal_tuple_from_heap_tuple(fc_bslot->base.tuple);
}

static inline void fc_tts_buffer_heap_store_tuple(TupleTableSlot *fc_slot, HeapTuple fc_tuple,
							Buffer fc_buffer, bool fc_transfer_pin)
{
	BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

	if (TTS_SHOULDFREE(fc_slot))
	{
		/* 物化的槽不应该有缓冲区需要释放 */
		Assert(!BufferIsValid(fc_bslot->buffer));

		heap_freetuple(fc_bslot->base.tuple);
		fc_slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
	}

	fc_slot->tts_flags &= ~TTS_FLAG_EMPTY;
	fc_slot->tts_nvalid = 0;
	fc_bslot->base.tuple = fc_tuple;
	fc_bslot->base.off = 0;
	fc_slot->tts_tid = fc_tuple->t_self;

	/*
	 * 如果元组位于磁盘页面上，只要我们持有指向它的指针，就保持页面锁定。我们假设调用者已经持有这样的锁。如果transfer_pin为真，我们将把该锁转移到这个槽上，否则我们将自己再次锁定它。
	 *
	 * 该代码旨在优化之前在同一磁盘页面上持有元组的槽的情况：在这种情况下，释放和重新获取锁会浪费周期。这是在顺序扫描期间常见的情况，因此值得花费精力。
	 */
	if (fc_bslot->buffer != fc_buffer)
	{
		if (BufferIsValid(fc_bslot->buffer))
			ReleaseBuffer(fc_bslot->buffer);

		fc_bslot->buffer = fc_buffer;

		if (!fc_transfer_pin && BufferIsValid(fc_buffer))
			IncrBufferRefCount(fc_buffer);
	}
	else if (fc_transfer_pin && BufferIsValid(fc_buffer))
	{
		/*
		 * 在transfer_pin模式下，调用者不会了解同一页面优化，因此我们必须释放其锁。
		 */
		ReleaseBuffer(fc_buffer);
	}
}

/*
 * slot_deform_heap_tuple
 *		给定一个TupleTableSlot，从槽的物理元组中提取数据到其Datum/isnull数组。数据从nattrs的列提取（调用者必须确保这是一个合法的列号）。
 *
 *		这本质上是heap_deform_tuple的增量版本：在每次调用中，我们提取所需列的属性，而不重新计算先前提取属性的信息。
 *		slot->tts_nvalid是已经提取的属性数量。
 *
 * 这被标记为始终内联，因此不同类型的槽的不同行为会被优化掉。
 */
static pg_attribute_always_inline void fc_slot_deform_heap_tuple(TupleTableSlot *fc_slot, HeapTuple fc_tuple, uint32 *fc_offp,
					   int fc_natts)
{
	TupleDesc	fc_tupleDesc = fc_slot->tts_tupleDescriptor;
	Datum	   *fc_values = fc_slot->tts_values;
	bool	   *fc_isnull = fc_slot->tts_isnull;
	HeapTupleHeader fc_tup = fc_tuple->t_data;
	bool		fc_hasnulls = HeapTupleHasNulls(fc_tuple);
	int			fc_attnum;
	char	   *fc_tp;				/* 指向元组数据的指针 */
	uint32		fc_off;			/* 在元组数据中的偏移量 */
	bits8	   *fc_bp = fc_tup->t_bits;	/* 指向元组中 null 位图的指针 */
	bool		fc_slow;			/* 我们可以使用/设置 attcacheoff 吗？ */

	/* 我们只能提取元组拥有的属性数量。 */
	fc_natts = Min(HeapTupleHeaderGetNatts(fc_tuple->t_data), fc_natts);

	/*
	 * 检查这是对该元组的第一次调用，并初始化或恢复循环状态。
	 */
	fc_attnum = fc_slot->tts_nvalid;
	if (fc_attnum == 0)
	{
		/* 从第一个属性开始 */
		fc_off = 0;
		fc_slow = false;
	}
	else
	{
		/* 从先前的执行中恢复状态 */
		fc_off = *fc_offp;
		fc_slow = TTS_SLOW(fc_slot);
	}

	fc_tp = (char *) fc_tup + fc_tup->t_hoff;

	for (; fc_attnum < fc_natts; fc_attnum++)
	{
		Form_pg_attribute fc_thisatt = TupleDescAttr(fc_tupleDesc, fc_attnum);

		if (fc_hasnulls && att_isnull(fc_attnum, fc_bp))
		{
			fc_values[fc_attnum] = (Datum) 0;
			fc_isnull[fc_attnum] = true;
			fc_slow = true;		/* 不能再使用 attcacheoff */
			continue;
		}

		fc_isnull[fc_attnum] = false;

		if (!fc_slow && fc_thisatt->attcacheoff >= 0)
			fc_off = fc_thisatt->attcacheoff;
		else if (fc_thisatt->attlen == -1)
		{
			/*
			 * 我们只能缓存 varlena 属性的偏移量，如果偏移量已经适当对齐，
			 * 以确保在任何情况下都没有填充字节：这样偏移量将适用于对齐或未对齐的值。
			 */
			if (!fc_slow &&
				fc_off == att_align_nominal(fc_off, fc_thisatt->attalign))
				fc_thisatt->attcacheoff = fc_off;
			else
			{
				fc_off = att_align_pointer(fc_off, fc_thisatt->attalign, -1,
										fc_tp + fc_off);
				fc_slow = true;
			}
		}
		else
		{
			/* 不是 varlena，因此可以安全使用 att_align_nominal */
			fc_off = att_align_nominal(fc_off, fc_thisatt->attalign);

			if (!fc_slow)
				fc_thisatt->attcacheoff = fc_off;
		}

		fc_values[fc_attnum] = fetchatt(fc_thisatt, fc_tp + fc_off);

		fc_off = att_addlength_pointer(fc_off, fc_thisatt->attlen, fc_tp + fc_off);

		if (fc_thisatt->attlen <= 0)
			fc_slow = true;		/* 不能再使用 attcacheoff */
	}

	/*
	 * 保存下次执行的状态
	 */
	fc_slot->tts_nvalid = fc_attnum;
	*fc_offp = fc_off;
	if (fc_slow)
		fc_slot->tts_flags |= TTS_FLAG_SLOW;
	else
		fc_slot->tts_flags &= ~TTS_FLAG_SLOW;
}


const TupleTableSlotOps TTSOpsVirtual = {
	.base_slot_size = sizeof(VirtualTupleTableSlot),
	.init = fc_tts_virtual_init,
	.release = fc_tts_virtual_release,
	.clear = fc_tts_virtual_clear,
	.getsomeattrs = fc_tts_virtual_getsomeattrs,
	.getsysattr = fc_tts_virtual_getsysattr,
	.materialize = fc_tts_virtual_materialize,
	.copyslot = fc_tts_virtual_copyslot,

	/*
	 * 虚拟元组表槽不能“拥有”堆元组或最小元组。
	 */
	.get_heap_tuple = NULL,
	.get_minimal_tuple = NULL,
	.copy_heap_tuple = fc_tts_virtual_copy_heap_tuple,
	.copy_minimal_tuple = fc_tts_virtual_copy_minimal_tuple
};

const TupleTableSlotOps TTSOpsHeapTuple = {
	.base_slot_size = sizeof(HeapTupleTableSlot),
	.init = fc_tts_heap_init,
	.release = fc_tts_heap_release,
	.clear = fc_tts_heap_clear,
	.getsomeattrs = fc_tts_heap_getsomeattrs,
	.getsysattr = fc_tts_heap_getsysattr,
	.materialize = fc_tts_heap_materialize,
	.copyslot = fc_tts_heap_copyslot,
	.get_heap_tuple = fc_tts_heap_get_heap_tuple,

	/* 堆元组表槽不能“拥有”最小元组。 */
	.get_minimal_tuple = NULL,
	.copy_heap_tuple = fc_tts_heap_copy_heap_tuple,
	.copy_minimal_tuple = fc_tts_heap_copy_minimal_tuple
};

const TupleTableSlotOps TTSOpsMinimalTuple = {
	.base_slot_size = sizeof(MinimalTupleTableSlot),
	.init = fc_tts_minimal_init,
	.release = fc_tts_minimal_release,
	.clear = fc_tts_minimal_clear,
	.getsomeattrs = fc_tts_minimal_getsomeattrs,
	.getsysattr = fc_tts_minimal_getsysattr,
	.materialize = fc_tts_minimal_materialize,
	.copyslot = fc_tts_minimal_copyslot,

	/* 最小元组表槽不能“拥有”堆元组。 */
	.get_heap_tuple = NULL,
	.get_minimal_tuple = fc_tts_minimal_get_minimal_tuple,
	.copy_heap_tuple = fc_tts_minimal_copy_heap_tuple,
	.copy_minimal_tuple = fc_tts_minimal_copy_minimal_tuple
};

const TupleTableSlotOps TTSOpsBufferHeapTuple = {
	.base_slot_size = sizeof(BufferHeapTupleTableSlot),
	.init = fc_tts_buffer_heap_init,
	.release = fc_tts_buffer_heap_release,
	.clear = fc_tts_buffer_heap_clear,
	.getsomeattrs = fc_tts_buffer_heap_getsomeattrs,
	.getsysattr = fc_tts_buffer_heap_getsysattr,
	.materialize = fc_tts_buffer_heap_materialize,
	.copyslot = fc_tts_buffer_heap_copyslot,
	.get_heap_tuple = fc_tts_buffer_heap_get_heap_tuple,

	/* 缓冲区堆元组表槽不能“拥有”最小元组。 */
	.get_minimal_tuple = NULL,
	.copy_heap_tuple = fc_tts_buffer_heap_copy_heap_tuple,
	.copy_minimal_tuple = fc_tts_buffer_heap_copy_minimal_tuple
};


/* ----------------------------------------------------------------
 *				  元组表创建/删除函数
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *		MakeTupleTableSlot
 *
 *		基本例程，创建一个空的给定TupleTableSlotType的TupleTableSlot。如果指定了tupleDesc，则槽的描述符在其生命周期内是固定的，从而提高了一些效率。如果这不理想，请传递NULL。
 * --------------------------------
 */
TupleTableSlot * MakeTupleTableSlot(TupleDesc fc_tupleDesc,
				   const TupleTableSlotOps *fc_tts_ops)
{
	Size		fc_basesz,
				fc_allocsz;
	TupleTableSlot *fc_slot;

	fc_basesz = fc_tts_ops->base_slot_size;

	/*
	 * 当指定固定描述符时，我们可以通过一次性分配整个槽来减少开销。
	 */
	if (fc_tupleDesc)
		fc_allocsz = MAXALIGN(fc_basesz) +
			MAXALIGN(fc_tupleDesc->natts * sizeof(Datum)) +
			MAXALIGN(fc_tupleDesc->natts * sizeof(bool));
	else
		fc_allocsz = fc_basesz;

	fc_slot = palloc0(fc_allocsz);
	/* 为优化目的的const，可以在分配时修改 */
	*((const TupleTableSlotOps **) &fc_slot->tts_ops) = fc_tts_ops;
	fc_slot->type = T_TupleTableSlot;
	fc_slot->tts_flags |= TTS_FLAG_EMPTY;
	if (fc_tupleDesc != NULL)
		fc_slot->tts_flags |= TTS_FLAG_FIXED;
	fc_slot->tts_tupleDescriptor = fc_tupleDesc;
	fc_slot->tts_mcxt = CurrentMemoryContext;
	fc_slot->tts_nvalid = 0;

	if (fc_tupleDesc != NULL)
	{
		fc_slot->tts_values = (Datum *)
			(((char *) fc_slot)
			 + MAXALIGN(fc_basesz));
		fc_slot->tts_isnull = (bool *)
			(((char *) fc_slot)
			 + MAXALIGN(fc_basesz)
			 + MAXALIGN(fc_tupleDesc->natts * sizeof(Datum)));

		PinTupleDesc(fc_tupleDesc);
	}

	/*
	 * 并允许槽类型特定的初始化。
	 */
	fc_slot->tts_ops->init(fc_slot);

	return fc_slot;
}

/* --------------------------------
 *		ExecAllocTableSlot
 *
 *		在元组表（仅仅是一个列表）内创建一个元组表槽。
 * --------------------------------
 */
TupleTableSlot * ExecAllocTableSlot(List **fc_tupleTable, TupleDesc fc_desc,
				   const TupleTableSlotOps *fc_tts_ops)
{
	TupleTableSlot *fc_slot = MakeTupleTableSlot(fc_desc, fc_tts_ops);

	*fc_tupleTable = lappend(*fc_tupleTable, fc_slot);

	return fc_slot;
}

/* --------------------------------
 *		ExecResetTupleTable
 *
 *		这将释放元组表持有的任何资源（缓冲区锁、tupdesc引用计数），并可选择释放元组表数据结构占用的内存。
 *		预计该例程应由ExecEndPlan()调用。
 * --------------------------------
 */
void ExecResetTupleTable(List *fc_tupleTable,	/* 元组表 */
					bool fc_shouldFree)	/* 如果我们应该释放内存则为true */
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_tupleTable)
	{
		TupleTableSlot *fc_slot = lfirst_node(TupleTableSlot, fc_lc);

		/* 始终释放资源并将槽重置为空 */
		ExecClearTuple(fc_slot);
		fc_slot->tts_ops->release(fc_slot);
		if (fc_slot->tts_tupleDescriptor)
		{
			ReleaseTupleDesc(fc_slot->tts_tupleDescriptor);
			fc_slot->tts_tupleDescriptor = NULL;
		}

		/* 如果shouldFree，则释放槽本身占用的内存 */
		if (fc_shouldFree)
		{
			if (!TTS_FIXED(fc_slot))
			{
				if (fc_slot->tts_values)
					pfree(fc_slot->tts_values);
				if (fc_slot->tts_isnull)
					pfree(fc_slot->tts_isnull);
			}
			pfree(fc_slot);
		}
	}

	/* 如果shouldFree，则释放列表结构 */
	if (fc_shouldFree)
		list_free(fc_tupleTable);
}

/* --------------------------------
 *		MakeSingleTupleTableSlot
 *
 *		这是一个方便的例程，用于需要一个独立的TupleTableSlot，而不是从主执行器元组表获得的操作。它创建一个给定TupleTableSlotType的单槽，并初始化为使用给定的元组描述符。
 * --------------------------------
 */
TupleTableSlot * MakeSingleTupleTableSlot(TupleDesc fc_tupdesc,
						 const TupleTableSlotOps *fc_tts_ops)
{
	TupleTableSlot *fc_slot = MakeTupleTableSlot(fc_tupdesc, fc_tts_ops);

	return fc_slot;
}

/* --------------------------------
 *		ExecDropSingleTupleTableSlot
 *
 *		释放由MakeSingleTupleTableSlot创建的TupleTableSlot。
 *		不要在属于元组表列表的槽上使用此操作！
 * --------------------------------
 */
void ExecDropSingleTupleTableSlot(TupleTableSlot *fc_slot)
{
	/* 这应与ExecResetTupleTable对一个槽的处理相匹配 */
	Assert(IsA(fc_slot, TupleTableSlot));
	ExecClearTuple(fc_slot);
	fc_slot->tts_ops->release(fc_slot);
	if (fc_slot->tts_tupleDescriptor)
		ReleaseTupleDesc(fc_slot->tts_tupleDescriptor);
	if (!TTS_FIXED(fc_slot))
	{
		if (fc_slot->tts_values)
			pfree(fc_slot->tts_values);
		if (fc_slot->tts_isnull)
			pfree(fc_slot->tts_isnull);
	}
	pfree(fc_slot);
}


/* ----------------------------------------------------------------
 *				  元组表槽访问函数
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *		ExecSetSlotDescriptor
 *
 *		此函数用于设置与槽的元组关联的元组描述符。传递的描述符的生命周期必须至少与槽相等。如果它是一个带引用计数的描述符，则当槽持有引用时引用计数将增加。
 * --------------------------------
 */
void ExecSetSlotDescriptor(TupleTableSlot *fc_slot, /* 要更改的槽 */
					  TupleDesc fc_tupdesc)	/* 新元组描述符 */
{
	Assert(!TTS_FIXED(fc_slot));

	/* 为了安全，确保在更改之前插槽是空的 */
	ExecClearTuple(fc_slot);

	/*
	 * 释放任何旧的描述符。如果存在，则还释放旧的 Datum/isnull 数组
	 * （我们不去检查它们是否可以被重用）。
	 */
	if (fc_slot->tts_tupleDescriptor)
		ReleaseTupleDesc(fc_slot->tts_tupleDescriptor);

	if (fc_slot->tts_values)
		pfree(fc_slot->tts_values);
	if (fc_slot->tts_isnull)
		pfree(fc_slot->tts_isnull);

	/*
	 * 安装新的描述符；如果它是引用计数的，则增加其引用计数。
	 */
	fc_slot->tts_tupleDescriptor = fc_tupdesc;
	PinTupleDesc(fc_tupdesc);

	/*
	 * 分配适当大小的 Datum/isnull 数组。这些必须与插槽具有相同的生命周期，
	 * 所以在插槽的上下文中分配。
	 */
	fc_slot->tts_values = (Datum *)
		MemoryContextAlloc(fc_slot->tts_mcxt, fc_tupdesc->natts * sizeof(Datum));
	fc_slot->tts_isnull = (bool *)
		MemoryContextAlloc(fc_slot->tts_mcxt, fc_tupdesc->natts * sizeof(bool));
}

/* --------------------------------
 *		ExecStoreHeapTuple
 *
 *		该函数用于将实时物理元组存储到元组表中指定的插槽。
 *
 *		tuple:	要存储的元组
 *		slot:	要存储的 TTSOpsHeapTuple 类型插槽
 *		shouldFree: 如果 ExecClearTuple 在处理完元组后应该 pfree() 该元组，则为 true
 *
 * shouldFree 通常为实时构建的元组设置为 'true'。但是在引用的元组保留在
 * 属于低级执行器 Proc 节点的元组表插槽中时，可以为 'false'。
 * 在这种情况下，低级插槽保留所有权并负责最终释放元组。
 * 当使用此方法时，我们必须确保上层 Proc 节点会比下层 Proc 节点更早失去对
 * 元组的兴趣！如果不确定，请使用 heap_copytuple 复制低级元组，并让上层
 * 表插槽拥有该副本！
 *
 * 返回值就是传入的插槽指针。
 *
 * 如果目标插槽不能保证是 TTSOpsHeapTuple 类型插槽，请使用
 * 更昂贵的 ExecForceStoreHeapTuple()。
 * --------------------------------
 */
TupleTableSlot * ExecStoreHeapTuple(HeapTuple fc_tuple,
				   TupleTableSlot *fc_slot,
				   bool fc_shouldFree)
{
	/*
	 * 健康检查
	 */
	Assert(fc_tuple != NULL);
	Assert(fc_slot != NULL);
	Assert(fc_slot->tts_tupleDescriptor != NULL);

	if (unlikely(!TTS_IS_HEAPTUPLE(fc_slot)))
		elog(ERROR, "trying to store a heap tuple into wrong type of slot");
	fc_tts_heap_store_tuple(fc_slot, fc_tuple, fc_shouldFree);

	fc_slot->tts_tableOid = fc_tuple->t_tableOid;

	return fc_slot;
}

/* --------------------------------
 *		ExecStoreBufferHeapTuple
 *
 *		该函数用于将磁盘上的物理元组从缓冲区存储到元组表中的指定插槽。
 *
 *		tuple:	要存储的元组
 *		slot:	要存储的 TTSOpsBufferHeapTuple 类型插槽
 *		buffer: 如果元组在磁盘页面中，则为磁盘缓冲区，否则为 InvalidBuffer
 *
 * 元组表代码在缓冲区上获取了一个锁定，该锁定将在插槽被清除之前保持，以确保
 * 元组不会消失。
 *
 * 返回值就是传入的插槽指针。
 *
 * 如果目标插槽不能保证是 TTSOpsBufferHeapTuple 类型插槽，
 * 请使用更昂贵的 ExecForceStoreHeapTuple()。
 * --------------------------------
 */
TupleTableSlot * ExecStoreBufferHeapTuple(HeapTuple fc_tuple,
						 TupleTableSlot *fc_slot,
						 Buffer fc_buffer)
{
	/*
	 * 健康检查
	 */
	Assert(fc_tuple != NULL);
	Assert(fc_slot != NULL);
	Assert(fc_slot->tts_tupleDescriptor != NULL);
	Assert(BufferIsValid(fc_buffer));

	if (unlikely(!TTS_IS_BUFFERTUPLE(fc_slot)))
		elog(ERROR, "trying to store an on-disk heap tuple into wrong type of slot");
	fc_tts_buffer_heap_store_tuple(fc_slot, fc_tuple, fc_buffer, false);

	fc_slot->tts_tableOid = fc_tuple->t_tableOid;

	return fc_slot;
}

/*
 * 类似 ExecStoreBufferHeapTuple，但将现有的锁定从调用者转移到插槽，
 * 即调用者不需要，并且可能不需要，释放该锁定。
 */
TupleTableSlot * ExecStorePinnedBufferHeapTuple(HeapTuple fc_tuple,
							   TupleTableSlot *fc_slot,
							   Buffer fc_buffer)
{
	/*
	 * 健康检查
	 */
	Assert(fc_tuple != NULL);
	Assert(fc_slot != NULL);
	Assert(fc_slot->tts_tupleDescriptor != NULL);
	Assert(BufferIsValid(fc_buffer));

	if (unlikely(!TTS_IS_BUFFERTUPLE(fc_slot)))
		elog(ERROR, "trying to store an on-disk heap tuple into wrong type of slot");
	fc_tts_buffer_heap_store_tuple(fc_slot, fc_tuple, fc_buffer, true);

	fc_slot->tts_tableOid = fc_tuple->t_tableOid;

	return fc_slot;
}

/*
 * 将最小元组存储到 TTSOpsMinimalTuple 类型插槽中。
 *
 * 如果目标插槽不能保证是 TTSOpsMinimalTuple 类型插槽，
 * 请使用更昂贵的 ExecForceStoreMinimalTuple()。
 */
TupleTableSlot * ExecStoreMinimalTuple(MinimalTuple fc_mtup,
					  TupleTableSlot *fc_slot,
					  bool fc_shouldFree)
{
	/*
	 * 健康检查
	 */
	Assert(fc_mtup != NULL);
	Assert(fc_slot != NULL);
	Assert(fc_slot->tts_tupleDescriptor != NULL);

	if (unlikely(!TTS_IS_MINIMALTUPLE(fc_slot)))
		elog(ERROR, "trying to store a minimal tuple into wrong type of slot");
	fc_tts_minimal_store_tuple(fc_slot, fc_mtup, fc_shouldFree);

	return fc_slot;
}

/*
 * 将 HeapTuple 存储到任何类型的插槽中，如果必要则执行转换。
 */
void ExecForceStoreHeapTuple(HeapTuple fc_tuple,
						TupleTableSlot *fc_slot,
						bool fc_shouldFree)
{
	if (TTS_IS_HEAPTUPLE(fc_slot))
	{
		ExecStoreHeapTuple(fc_tuple, fc_slot, fc_shouldFree);
	}
	else if (TTS_IS_BUFFERTUPLE(fc_slot))
	{
		MemoryContext fc_oldContext;
		BufferHeapTupleTableSlot *fc_bslot = (BufferHeapTupleTableSlot *) fc_slot;

		ExecClearTuple(fc_slot);
		fc_slot->tts_flags &= ~TTS_FLAG_EMPTY;
		fc_oldContext = MemoryContextSwitchTo(fc_slot->tts_mcxt);
		fc_bslot->base.tuple = heap_copytuple(fc_tuple);
		fc_slot->tts_flags |= TTS_FLAG_SHOULDFREE;
		MemoryContextSwitchTo(fc_oldContext);

		if (fc_shouldFree)
			pfree(fc_tuple);
	}
	else
	{
		ExecClearTuple(fc_slot);
		heap_deform_tuple(fc_tuple, fc_slot->tts_tupleDescriptor,
						  fc_slot->tts_values, fc_slot->tts_isnull);
		ExecStoreVirtualTuple(fc_slot);

		if (fc_shouldFree)
		{
			ExecMaterializeSlot(fc_slot);
			pfree(fc_tuple);
		}
	}
}

/*
 * 将 MinimalTuple 存储到任何类型的插槽中，如果必要则执行转换。
 */
void ExecForceStoreMinimalTuple(MinimalTuple fc_mtup,
						   TupleTableSlot *fc_slot,
						   bool fc_shouldFree)
{
	if (TTS_IS_MINIMALTUPLE(fc_slot))
	{
		fc_tts_minimal_store_tuple(fc_slot, fc_mtup, fc_shouldFree);
	}
	else
	{
		HeapTupleData fc_htup;

		ExecClearTuple(fc_slot);

		fc_htup.t_len = fc_mtup->t_len + MINIMAL_TUPLE_OFFSET;
		fc_htup.t_data = (HeapTupleHeader) ((char *) fc_mtup - MINIMAL_TUPLE_OFFSET);
		heap_deform_tuple(&fc_htup, fc_slot->tts_tupleDescriptor,
						  fc_slot->tts_values, fc_slot->tts_isnull);
		ExecStoreVirtualTuple(fc_slot);

		if (fc_shouldFree)
		{
			ExecMaterializeSlot(fc_slot);
			pfree(fc_mtup);
		}
	}
}

/* --------------------------------
 *		ExecStoreVirtualTuple
 *			将插槽标记为包含虚拟元组。
 *
 * 加载虚拟元组数据到插槽的协议是：
 *		* 调用 ExecClearTuple 标记插槽为空。
 *		* 将数据存储到 Datum/isnull 数组中。
 *		* 调用 ExecStoreVirtualTuple 标记插槽有效。
 * 这有些不整洁，但可以避免一次数据复制。
 * --------------------------------
 */
TupleTableSlot * ExecStoreVirtualTuple(TupleTableSlot *fc_slot)
{
	/*
	 * 健康检查
	 */
	Assert(fc_slot != NULL);
	Assert(fc_slot->tts_tupleDescriptor != NULL);
	Assert(TTS_EMPTY(fc_slot));

	fc_slot->tts_flags &= ~TTS_FLAG_EMPTY;
	fc_slot->tts_nvalid = fc_slot->tts_tupleDescriptor->natts;

	return fc_slot;
}

/* --------------------------------
 *		ExecStoreAllNullTuple
 *			设置插槽以包含每列中的空值。
 *
 * 乍一看，这可能听起来就像 ExecClearTuple，但它完全不同：
 * 插槽最终是满的，而不是空的。
 * --------------------------------
 */
TupleTableSlot * ExecStoreAllNullTuple(TupleTableSlot *fc_slot)
{
	/*
	 * 健康检查
	 */
	Assert(fc_slot != NULL);
	Assert(fc_slot->tts_tupleDescriptor != NULL);

	/* 清除任何旧内容 */
	ExecClearTuple(fc_slot);

	/*
	 * 用空值填充虚拟元组的所有列
	 */
	MemSet(fc_slot->tts_values, 0,
		   fc_slot->tts_tupleDescriptor->natts * sizeof(Datum));
	memset(fc_slot->tts_isnull, true,
		   fc_slot->tts_tupleDescriptor->natts * sizeof(bool));

	return ExecStoreVirtualTuple(fc_slot);
}

/*
 * 将 HeapTuple 以 datum 形式存储到插槽中。这始终需要
 * 进行解构并以虚拟形式存储。
 *
 * 在插槽被物化之前，插槽的内容依赖于 datum。
 */
void ExecStoreHeapTupleDatum(Datum fc_data, TupleTableSlot *fc_slot)
{
	HeapTupleData fc_tuple = {0};
	HeapTupleHeader fc_td;

	fc_td = DatumGetHeapTupleHeader(fc_data);

	fc_tuple.t_len = HeapTupleHeaderGetDatumLength(fc_td);
	fc_tuple.t_self = fc_td->t_ctid;
	fc_tuple.t_data = fc_td;

	ExecClearTuple(fc_slot);

	heap_deform_tuple(&fc_tuple, fc_slot->tts_tupleDescriptor,
					  fc_slot->tts_values, fc_slot->tts_isnull);
	ExecStoreVirtualTuple(fc_slot);
}

/*
 * ExecFetchSlotHeapTuple - 获取表示插槽内容的 HeapTuple
 *
 * 返回的 HeapTuple 尽可能准确地表示插槽的内容。
 *
 * 如果 materialize 为 true，插槽的内容将脱离底层存储
 * （即释放所有缓冲区锁定，在插槽的上下文中分配内存）。
 *
 * 如果 shouldFree 不为 NULL，则如果返回的元组是在调用的内存上下文中分配的，将设置为 true，
 * 必须由调用者释放（通过显式的 pfree() 或内存上下文重置）。
 *
 * 注意：如果 materialize 为 true，则允许对返回的元组进行修改。
 * 但是，这种修改是否影响插槽的内容取决于插槽的类型。
 * 虽然这不是最好的行为，但所有这样的修改正在被逐步移除。
 */
HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *fc_slot, bool fc_materialize, bool *fc_shouldFree)
{
	/*
	 * 健康检查
	 */
	Assert(fc_slot != NULL);
	Assert(!TTS_EMPTY(fc_slot));

	
/* 使元组具象化，以便槽“拥有”它（如果需要）。 */
	if (fc_materialize)
		fc_slot->tts_ops->materialize(fc_slot);

	if (fc_slot->tts_ops->get_heap_tuple == NULL)
	{
		if (fc_shouldFree)
			*fc_shouldFree = true;
		return fc_slot->tts_ops->copy_heap_tuple(fc_slot);
	}
	else
	{
		if (fc_shouldFree)
			*fc_shouldFree = false;
		return fc_slot->tts_ops->get_heap_tuple(fc_slot);
	}
}

/* -------------------------------
 * ExecFetchSlotMinimalTuple
 * 获取槽的最小物理元组。
 *
 * 如果给定的元组表槽可以容纳最小元组，通过非NULL的 get_minimal_tuple 回调指示，函数返回由该回调返回的最小元组。它假设回调返回的最小元组是“由槽拥有”的，即槽负责释放元组占用的内存。因此，它将 *shouldFree 设置为 false，表示调用者不应释放最小元组占用的内存。在这种情况下，返回的最小元组应被视为只读。
 *
 * 如果该回调不受支持，它将调用 copy_minimal_tuple 回调，该回调预计返回表示槽内容的最小元组的副本。在这种情况下，*shouldFree 设置为 true，提示调用者应释放最小元组占用的内存。在这种情况下，返回的最小元组可以被修改。
 * -------------------------------
 */
MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *fc_slot,
						  bool *fc_shouldFree)
{
	/*
	 * 健康检查
	 */
	Assert(fc_slot != NULL);
	Assert(!TTS_EMPTY(fc_slot));

	if (fc_slot->tts_ops->get_minimal_tuple)
	{
		if (fc_shouldFree)
			*fc_shouldFree = false;
		return fc_slot->tts_ops->get_minimal_tuple(fc_slot);
	}
	else
	{
		if (fc_shouldFree)
			*fc_shouldFree = true;
		return fc_slot->tts_ops->copy_minimal_tuple(fc_slot);
	}
}

/* -------------------------------
 * ExecFetchSlotHeapTupleDatum
 * 获取槽的元组作为复合类型 Datum。
 *
 * 结果始终在调用者的内存上下文中新分配。 */
Datum ExecFetchSlotHeapTupleDatum(TupleTableSlot *fc_slot)
{
	HeapTuple	fc_tup;
	TupleDesc	fc_tupdesc;
	bool		fc_shouldFree;
	Datum		fc_ret;

	/* 获取槽的内容，以常规物理元组形式 */
	fc_tup = ExecFetchSlotHeapTuple(fc_slot, false, &fc_shouldFree);
	fc_tupdesc = fc_slot->tts_tupleDescriptor;

	/* 转换为 Datum 形式 */
	fc_ret = heap_copy_tuple_as_datum(fc_tup, fc_tupdesc);

	if (fc_shouldFree)
		pfree(fc_tup);

	return fc_ret;
}

/* ---------------------------------------------------------------
 * 便利初始化例程
 * ---------------------------------------------------------------
 */

/* ----------------
 * ExecInitResultTypeTL
 *
 * 初始化结果类型，使用计划节点的目标列表。
 * ----------------
 */
void ExecInitResultTypeTL(PlanState *fc_planstate)
{
	TupleDesc	fc_tupDesc = ExecTypeFromTL(fc_planstate->plan->targetlist);

	fc_planstate->ps_ResultTupleDesc = fc_tupDesc;
}

/* -------------------------------
 * ExecInit{Result,Scan,Extra}TupleSlot[TL]
 *
 * 这些是便利例程，用于在继承适当状态的节点中初始化指定的槽。 ExecInitExtraTupleSlot 用于初始化特殊用途槽。
 * -------------------------------
 */

/* ----------------
 * ExecInitResultTupleSlotTL
 *
 * 初始化结果元组槽，使用之前通过 ExecInitResultTypeTL() 计算的元组描述符。
 * ----------------
 */
void ExecInitResultSlot(PlanState *fc_planstate, const TupleTableSlotOps *fc_tts_ops)
{
	TupleTableSlot *fc_slot;

	fc_slot = ExecAllocTableSlot(&fc_planstate->state->es_tupleTable,
							  fc_planstate->ps_ResultTupleDesc, fc_tts_ops);
	fc_planstate->ps_ResultTupleSlot = fc_slot;

	fc_planstate->resultopsfixed = fc_planstate->ps_ResultTupleDesc != NULL;
	fc_planstate->resultops = fc_tts_ops;
	fc_planstate->resultopsset = true;
}

/* ----------------
 * ExecInitResultTupleSlotTL
 *
 * 初始化结果元组槽，使用计划节点的目标列表。
 * ----------------
 */
void ExecInitResultTupleSlotTL(PlanState *fc_planstate,
						  const TupleTableSlotOps *fc_tts_ops)
{
	ExecInitResultTypeTL(fc_planstate);
	ExecInitResultSlot(fc_planstate, fc_tts_ops);
}

/* ----------------
 * ExecInitScanTupleSlot
 * ----------------
 */
void ExecInitScanTupleSlot(EState *fc_estate, ScanState *fc_scanstate,
					  TupleDesc fc_tupledesc, const TupleTableSlotOps *fc_tts_ops)
{
	fc_scanstate->ss_ScanTupleSlot = ExecAllocTableSlot(&fc_estate->es_tupleTable,
													 fc_tupledesc, fc_tts_ops);
	fc_scanstate->ps.scandesc = fc_tupledesc;
	fc_scanstate->ps.scanopsfixed = fc_tupledesc != NULL;
	fc_scanstate->ps.scanops = fc_tts_ops;
	fc_scanstate->ps.scanopsset = true;
}

/* ----------------
 * ExecInitExtraTupleSlot
 *
 * 返回一个新创建的槽。如果 tupledesc 非 NULL，则槽将具有该固定 tupledesc。否则，调用者需要在使用之前使用 ExecSetSlotDescriptor() 设置描述符。
 * ----------------
 */  
TupleTableSlot * ExecInitExtraTupleSlot(EState *fc_estate,
					   TupleDesc fc_tupledesc,
					   const TupleTableSlotOps *fc_tts_ops)
{
	return ExecAllocTableSlot(&fc_estate->es_tupleTable, fc_tupledesc, fc_tts_ops);
}

/* ----------------
 *		ExecInitNullTupleSlot
 *
 * 构建一个包含给定类型的全NULL元组的槽。
 * 在执行外连接时，这用作输入元组的替代。
 * ----------------
 */
TupleTableSlot * ExecInitNullTupleSlot(EState *fc_estate, TupleDesc fc_tupType,
					  const TupleTableSlotOps *fc_tts_ops)
{
	TupleTableSlot *fc_slot = ExecInitExtraTupleSlot(fc_estate, fc_tupType, fc_tts_ops);

	return ExecStoreAllNullTuple(fc_slot);
}

/* ---------------------------------------------------------------
 *      用于设置/访问槽中属性的例程。
 * ---------------------------------------------------------------
 */

/*
 * 填充 TupleTableSlot 的缺失值。
 *
 * 之所以公开这一点只是因为它在 JIT 编译的元组
 * 变形中需要。除此之外，这个文件外没有调用者。
 */
void slot_getmissingattrs(TupleTableSlot *fc_slot, int fc_startAttNum, int fc_lastAttNum)
{
	AttrMissing *fc_attrmiss = NULL;

	if (fc_slot->tts_tupleDescriptor->constr)
		fc_attrmiss = fc_slot->tts_tupleDescriptor->constr->missing;

	if (!fc_attrmiss)
	{
		/* 完全没有缺失值数组，因此只需将所有内容填充为 NULL */
		memset(fc_slot->tts_values + fc_startAttNum, 0,
			   (fc_lastAttNum - fc_startAttNum) * sizeof(Datum));
		memset(fc_slot->tts_isnull + fc_startAttNum, 1,
			   (fc_lastAttNum - fc_startAttNum) * sizeof(bool));
	}
	else
	{
		int			fc_missattnum;

		/* 如果有缺失值数组，我们必须逐一处理它们 */
		for (fc_missattnum = fc_startAttNum;
			 fc_missattnum < fc_lastAttNum;
			 fc_missattnum++)
		{
			fc_slot->tts_values[fc_missattnum] = fc_attrmiss[fc_missattnum].am_value;
			fc_slot->tts_isnull[fc_missattnum] = !fc_attrmiss[fc_missattnum].am_present;
		}
	}
}

/*
 * slot_getsomeattrs_int - slot_getsomeattrs() 的主力
 */
void slot_getsomeattrs_int(TupleTableSlot *fc_slot, int fc_attnum)
{
	/* 检查调用者错误 */
	Assert(fc_slot->tts_nvalid < fc_attnum);	/* 在 slot_getsomeattrs 中检查 */
	Assert(fc_attnum > 0);

	if (unlikely(fc_attnum > fc_slot->tts_tupleDescriptor->natts))
		elog(ERROR, "invalid attribute number %d", fc_attnum);

	/* 从基础元组中获取尽可能多的属性。 */
	fc_slot->tts_ops->getsomeattrs(fc_slot, fc_attnum);

	/*
	 * 如果基础元组没有足够的属性，则元组描述符必须包含缺失的属性。
	 */
	if (unlikely(fc_slot->tts_nvalid < fc_attnum))
	{
		slot_getmissingattrs(fc_slot, fc_slot->tts_nvalid, fc_attnum);
		fc_slot->tts_nvalid = fc_attnum;
	}
}

/* ----------------------------------------------------------------
 *		ExecTypeFromTL
 *
 *		为目标列表的结果元组生成一个元组描述符。
 *		（必须传递解析/计划的 tlist，而不是 ExprState 的 tlist。）
 *		注意，结果中包括 resjunk 列（如果有的话）。
 *
 *		目前大约有4个不同的地方创建
 *		TupleDescriptors。它们应该合并，或许
 *		被重写为调用 BuildDesc()。
 * ----------------------------------------------------------------
 */
TupleDesc ExecTypeFromTL(List *fc_targetList)
{
	return fc_ExecTypeFromTLInternal(fc_targetList, false);
}

/* ----------------------------------------------------------------
 *		ExecCleanTypeFromTL
 *
 *		与上面相同，但结果中省略了 resjunk 列。
 * ----------------------------------------------------------------
 */
TupleDesc ExecCleanTypeFromTL(List *fc_targetList)
{
	return fc_ExecTypeFromTLInternal(fc_targetList, true);
}

static TupleDesc fc_ExecTypeFromTLInternal(List *fc_targetList, bool fc_skipjunk)
{
	TupleDesc	fc_typeInfo;
	ListCell   *fc_l;
	int			fc_len;
	int			fc_cur_resno = 1;

	if (fc_skipjunk)
		fc_len = ExecCleanTargetListLength(fc_targetList);
	else
		fc_len = ExecTargetListLength(fc_targetList);
	fc_typeInfo = CreateTemplateTupleDesc(fc_len);

	foreach(fc_l, fc_targetList)
	{
		TargetEntry *fc_tle = lfirst(fc_l);

		if (fc_skipjunk && fc_tle->resjunk)
			continue;
		TupleDescInitEntry(fc_typeInfo,
						   fc_cur_resno,
						   fc_tle->resname,
						   exprType((Node *) fc_tle->expr),
						   exprTypmod((Node *) fc_tle->expr),
						   0);
		TupleDescInitEntryCollation(fc_typeInfo,
									fc_cur_resno,
									exprCollation((Node *) fc_tle->expr));
		fc_cur_resno++;
	}

	return fc_typeInfo;
}

/*
 * ExecTypeFromExprList - 从 Expr 列表构建一个元组描述符
 *
 * 这大致类似于 ExecTypeFromTL，但我们从裸表达式
 * 而不是 TargetEntrys 开始。元组描述符的列没有附加名字。
 */
TupleDesc ExecTypeFromExprList(List *fc_exprList)
{
	TupleDesc	fc_typeInfo;
	ListCell   *fc_lc;
	int			fc_cur_resno = 1;

	fc_typeInfo = CreateTemplateTupleDesc(list_length(fc_exprList));

	foreach(fc_lc, fc_exprList)
	{
		Node	   *fc_e = lfirst(fc_lc);

		TupleDescInitEntry(fc_typeInfo,
						   fc_cur_resno,
						   NULL,
						   exprType(fc_e),
						   exprTypmod(fc_e),
						   0);
		TupleDescInitEntryCollation(fc_typeInfo,
									fc_cur_resno,
									exprCollation(fc_e));
		fc_cur_resno++;
	}

	return fc_typeInfo;
}

/*
 * ExecTypeSetColNames - 在 RECORD TupleDesc 中设置列名
 *
 * 列名必须作为别名列表提供（字符串节点列表）。
 */
void ExecTypeSetColNames(TupleDesc fc_typeInfo, List *fc_namesList)
{
	int			fc_colno = 0;
	ListCell   *fc_lc;

	/* 只有在不再激活的 RECORD 类型中才能更改列名 */
	Assert(fc_typeInfo->tdtypeid == RECORDOID);
	Assert(fc_typeInfo->tdtypmod < 0);

	foreach(fc_lc, fc_namesList)
	{
		char	   *fc_cname = strVal(lfirst(fc_lc));
		Form_pg_attribute fc_attr;

		/* 防止列名列表过长（可能不会发生） */
		if (fc_colno >= fc_typeInfo->natts)
			break;
		fc_attr = TupleDescAttr(fc_typeInfo, fc_colno);
		fc_colno++;

		/*
		 * 对于空别名或已删除的列不执行任何操作（这些情况
		 * 在 RECORD 类型中可能也不会出现）
		 */
		if (fc_cname[0] == '\0' || fc_attr->attisdropped)
			continue;

		/* 好的，分配列名 */
		namestrcpy(&(fc_attr->attname), fc_cname);
	}
}

/*
 * BlessTupleDesc - 使完成的元组描述符可用于 SRFs
 *
 * 函数返回的 Rowtype Datums 必须包含有效的类型信息。
 * 如果 tupdesc 来自 relcache 条目，则会“免费”发生，
 * 但如果我们为临时 RECORD 数据类型制造了一个 tupdesc，
 * 则不会。在这种情况下，我们必须通知 typcache.c 存在该类型。
 */
TupleDesc BlessTupleDesc(TupleDesc fc_tupdesc)
{
	if (fc_tupdesc->tdtypeid == RECORDOID &&
		fc_tupdesc->tdtypmod < 0)
		assign_record_type_typmod(fc_tupdesc);

	return fc_tupdesc;				/* 只是为了便利的表示 */
}

/*
 * TupleDescGetAttInMetadata - 基于提供的 TupleDesc 构建一个 AttInMetadata 结构。
 * AttInMetadata 可与 C 字符串一起使用，以生成正确格式的元组。
 */
AttInMetadata *
TupleDescGetAttInMetadata(TupleDesc fc_tupdesc)
{
	int			fc_natts = fc_tupdesc->natts;
	int			fc_i;
	Oid			fc_atttypeid;
	Oid			fc_attinfuncid;
	FmgrInfo   *fc_attinfuncinfo;
	Oid		   *fc_attioparams;
	int32	   *fc_atttypmods;
	AttInMetadata *fc_attinmeta;

	fc_attinmeta = (AttInMetadata *) palloc(sizeof(AttInMetadata));

	/* “祝福”元组描述符，以便我们可以用它创建 rowtype datums */
	fc_attinmeta->tupdesc = BlessTupleDesc(fc_tupdesc);

	/*
	 * 收集稍后调用每个属性的 “in” 函数所需的信息
	 */
	fc_attinfuncinfo = (FmgrInfo *) palloc0(fc_natts * sizeof(FmgrInfo));
	fc_attioparams = (Oid *) palloc0(fc_natts * sizeof(Oid));
	fc_atttypmods = (int32 *) palloc0(fc_natts * sizeof(int32));

	for (fc_i = 0; fc_i < fc_natts; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);

		/* 忽略已删除的属性 */
		if (!fc_att->attisdropped)
		{
			fc_atttypeid = fc_att->atttypid;
			getTypeInputInfo(fc_atttypeid, &fc_attinfuncid, &fc_attioparams[fc_i]);
			fmgr_info(fc_attinfuncid, &fc_attinfuncinfo[fc_i]);
			fc_atttypmods[fc_i] = fc_att->atttypmod;
		}
	}
	fc_attinmeta->attinfuncs = fc_attinfuncinfo;
	fc_attinmeta->attioparams = fc_attioparams;
	fc_attinmeta->atttypmods = fc_atttypmods;

	return fc_attinmeta;
}

/*
 * BuildTupleFromCStrings - 给定 C 字符串形式的用户数据构建一个 HeapTuple。
 * values 是一个 C 字符串数组，针对返回元组的每个属性。
 * NULL 字符串指针表示我们想要创建一个 NULL 字段。
 */
HeapTuple BuildTupleFromCStrings(AttInMetadata *fc_attinmeta, char **fc_values)
{
	TupleDesc	fc_tupdesc = fc_attinmeta->tupdesc;
	int			fc_natts = fc_tupdesc->natts;
	Datum	   *fc_dvalues;
	bool	   *fc_nulls;
	int			fc_i;
	HeapTuple	fc_tuple;

	fc_dvalues = (Datum *) palloc(fc_natts * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_natts * sizeof(bool));

	/*
	 * 对每个非删除属性调用 “in” 函数，即使是 NULL，
	 * 以支持域。
	 */
	for (fc_i = 0; fc_i < fc_natts; fc_i++)
	{
		if (!TupleDescAttr(fc_tupdesc, fc_i)->attisdropped)
		{
			/* 非丢失的属性 */
			fc_dvalues[fc_i] = InputFunctionCall(&fc_attinmeta->attinfuncs[fc_i],
										   fc_values[fc_i],
										   fc_attinmeta->attioparams[fc_i],
										   fc_attinmeta->atttypmods[fc_i]);
			if (fc_values[fc_i] != NULL)
				fc_nulls[fc_i] = false;
			else
				fc_nulls[fc_i] = true;
		}
		else
		{
			/* 通过设置为 NULL 处理丢失的属性 */
			fc_dvalues[fc_i] = (Datum) 0;
			fc_nulls[fc_i] = true;
		}
	}

	/*
	 * 形成一个元组
	 */
	fc_tuple = heap_form_tuple(fc_tupdesc, fc_dvalues, fc_nulls);

	/*
	 * 释放本地分配的空间。 XXX 可能最好也释放
	 * 值为引用传递数据的空间。
	 */
	pfree(fc_dvalues);
	pfree(fc_nulls);

	return fc_tuple;
}

/*
 * HeapTupleHeaderGetDatum - 将 HeapTupleHeader 指针转换为 Datum。
 *
 * 这 *不能* 应用于磁盘上的元组；元组应该是
 * 由 heap_form_tuple 或其某个包装例程（如
 * BuildTupleFromCStrings）新建的。确保用于构建
 * 元组的 tupledesc 有一个适当“祝福”的行类型。
 *
 * 以前这是一个等同于 PointerGetDatum 的宏，依赖于
 * heap_form_tuple 填充复合 Datum 的适当元组头字段。
 * 但是，我们现在要求复合 Datums 不
 * 包含任何外部 TOAST 指针。我们不希望 heap_form_tuple 直接
 * 强制执行这一点；更具体地说，该规则仅适用于实际的 Datums
 * 而不适用于 HeapTuple 结构。因此，HeapTupleHeaderGetDatum 现在是
 * 一个函数，它检测是否存在外部夹层字段，
 * 如果有则构建一个带有内联字段的新元组。我们仍然需要
 * heap_form_tuple 来插入 Datum 头字段，因为否则这段代码将没有办法获取元组的 tupledesc。
 *
 * 请注意，如果我们确实构建了一个新元组，它是在当前
 * 内存上下文中进行 palloc 的。注意在初始
 * heap_form_tuple 等调用与调用 HeapTuple(Header)GetDatum
 * 之间更改上下文的代码。
 *
 * 对于性能关键的调用者，采取额外措施确保
 * heap_form_tuple 的输出中没有 TOAST 指针可能值得一试。
 * 然而，类型缓存查找及元组拆解/重组的成本可能会被 TOAST
 * 解引用的成本所覆盖，因此这种额外努力的好处将是
 * 微不足道的。
 *
 * XXX 创建包装函数，从字段值一步生成
 * 复合 Datum 可能更好。然而，使用现有 API 的代码已经足够多，
 * 我们不能很快去掉这个技巧。
 */
Datum HeapTupleHeaderGetDatum(HeapTupleHeader fc_tuple)
{
	Datum		fc_result;
	TupleDesc	fc_tupDesc;

	/* 如果元组中没有外部 TOAST 指针则不进行处理 */
	if (!HeapTupleHeaderHasExternal(fc_tuple))
		return PointerGetDatum(fc_tuple);

	/* 使用 heap_form_tuple 保存的类型数据查找行类型 */
	fc_tupDesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(fc_tuple),
									 HeapTupleHeaderGetTypMod(fc_tuple));

	/* 并进行扁平化处理 */
	fc_result = toast_flatten_tuple_to_datum(fc_tuple,
										  HeapTupleHeaderGetDatumLength(fc_tuple),
										  fc_tupDesc);

	ReleaseTupleDesc(fc_tupDesc);

	return fc_result;
}


/*
 * 发送元组到前端（或其他指定目的地） 
 * 就像它是一个 SELECT 结果。这些用于需要直接投影到
 * 目的地的实用命令，并且不需要或不想要完整的
 * 表函数能力。目前被 EXPLAIN 和 SHOW ALL 使用。
 */
TupOutputState *
begin_tup_output_tupdesc(DestReceiver *fc_dest,
						 TupleDesc fc_tupdesc,
						 const TupleTableSlotOps *fc_tts_ops)
{
	TupOutputState *fc_tstate;

	fc_tstate = (TupOutputState *) palloc(sizeof(TupOutputState));

	fc_tstate->slot = MakeSingleTupleTableSlot(fc_tupdesc, fc_tts_ops);
	fc_tstate->dest = fc_dest;

	fc_tstate->dest->rStartup(fc_tstate->dest, (int) CMD_SELECT, fc_tupdesc);

	return fc_tstate;
}

/*
 * 写入单个元组
 */
void do_tup_output(TupOutputState *fc_tstate, Datum *fc_values, bool *fc_isnull)
{
	TupleTableSlot *fc_slot = fc_tstate->slot;
	int			fc_natts = fc_slot->tts_tupleDescriptor->natts;

	/* 确保槽是空的 */
	ExecClearTuple(fc_slot);

	/* 插入数据 */
	memcpy(fc_slot->tts_values, fc_values, fc_natts * sizeof(Datum));
	memcpy(fc_slot->tts_isnull, fc_isnull, fc_natts * sizeof(bool));

	/* 标记槽为包含虚拟元组 */
	ExecStoreVirtualTuple(fc_slot);

	/* 将元组发送到接收方 */
	(void) fc_tstate->dest->receiveSlot(fc_slot, fc_tstate->dest);

	/* 清理 */
	ExecClearTuple(fc_slot);
}

/*
 * 写入一块文本，在换行符处断开
 *
 * 仅应与单个 TEXT 属性的 tupdesc 一起使用。
 */
void do_text_output_multiline(TupOutputState *fc_tstate, const char *fc_txt)
{
	Datum		fc_values[1];
	bool		fc_isnull[1] = {false};

	while (*fc_txt)
	{
		const char *fc_eol;
		int			fc_len;

		fc_eol = strchr(fc_txt, '\n');
		if (fc_eol)
		{
			fc_len = fc_eol - fc_txt;
			fc_eol++;
		}
		else
		{
			fc_len = strlen(fc_txt);
			fc_eol = fc_txt + fc_len;
		}

		fc_values[0] = PointerGetDatum(cstring_to_text_with_len(fc_txt, fc_len));
		do_tup_output(fc_tstate, fc_values, fc_isnull);
		pfree(DatumGetPointer(fc_values[0]));
		fc_txt = fc_eol;
	}
}

void end_tup_output(TupOutputState *fc_tstate)
{
	fc_tstate->dest->rShutdown(fc_tstate->dest);
	/* 注意，销毁目标不是我们的职责 */
	ExecDropSingleTupleTableSlot(fc_tstate->slot);
	pfree(fc_tstate);
}
